#include "oled.h"

static ssize_t oled_write(struct file *file, const char __user *data, size_t lenght, loff_t *pos);
static int oled_mmap(struct file *file, struct vm_area_struct *vma);
static long oled_ioctl32(struct file *file, unsigned int request, unsigned long arg);
static long oled_ioctl64(struct file *file, unsigned int request, unsigned long arg);

static int oled_probe(struct spi_device *spi);
static int oled_remove(struct spi_device *spi);

//设备号
static dev_t oled_num;
//cdev对象
static struct cdev oled_cdev;
//class对象
static struct class *oled_class;
//操作函数
static struct file_operations oled_ops = {
	.owner = THIS_MODULE,
	.write = oled_write,
	.mmap = oled_mmap,
	.unlocked_ioctl = oled_ioctl32,
	.compat_ioctl = oled_ioctl64,
};
//oled资源
static struct oled_handle oled_handle[OLED_NUMBER];

/* 匹配列表，用于设备树和平台驱动匹配 */
static const struct of_device_id oled_of_match[] = {
	{.compatible = "atk,oled"},
	{ /* Sentinel */ }
};
/* 传统匹配方式ID列表 */
static const struct spi_device_id oled_id[] = {
	{}
};
//SPI驱动
static struct spi_driver oled_drv = {
	.driver = {
		.name = "oled",
		.owner = THIS_MODULE,
		.pm = NULL,
		.of_match_table = oled_of_match,
	},
	.id_table = oled_id,
	.probe = oled_probe,
	.remove = oled_remove,
};

static int oled_write_cmd(struct oled_handle *oled, uint8_t *command, uint32_t lenght)
{
	int result;
	uint8_t *buffer;
	struct spi_message message;
	struct spi_transfer transfer;

	//分配发送缓存
	buffer = kzalloc(lenght, GFP_KERNEL);
	if(!buffer)
		return -ENOMEM;
	//将数据拷贝到buffer中
	memcpy(buffer, command, lenght);

	//拉低数据命令选择引脚，表示发送命令
	gpio_set_value(oled->dc_gpio, 0);

	//复位spi_transfer
	memset(&transfer, 0, sizeof(transfer));
	//初始化spi_message
	spi_message_init(&message);

	//发送缓存，不能使用全局变量或静态变量
	transfer.tx_buf = buffer;
	//接收缓存
	transfer.rx_buf = NULL;
	//传输的长度
	transfer.len = lenght;
	//将spi_transfer添加到spi_message队列
	spi_message_add_tail(&transfer, &message);
	
	//同步传输
	result = spi_sync(oled->spi_device, &message);

	//命令发送完成，拉高数据命令选择引脚
	gpio_set_value(oled->dc_gpio, 1);

	//释放发送缓存
	kfree(buffer);

	return result;
}

static int oled_write_data(struct oled_handle *oled, uint8_t *data, uint32_t lenght)
{
	int result;
	uint8_t *buffer;
	struct spi_message message;
	struct spi_transfer transfer;

	//分配发送缓存
	buffer = kzalloc(lenght, GFP_KERNEL);
	if(!buffer)
		return -ENOMEM;
	//将数据拷贝到buffer中
	memcpy(buffer, data, lenght);

	//拉高数据命令选择引脚，表示发送数据
	gpio_set_value(oled->dc_gpio, 1);

	//复位spi_transfer
	memset(&transfer, 0, sizeof(transfer));
	//初始化spi_message
	spi_message_init(&message);

	//发送缓存
	transfer.tx_buf = buffer;
	//接收缓存
	transfer.rx_buf = NULL;
	//传输的长度
	transfer.len = lenght;
	//将spi_transfer添加到spi_message队列
	spi_message_add_tail(&transfer, &message);
	
	//同步传输
	result = spi_sync(oled->spi_device, &message);

	//命令发送完成，继续拉高数据命令选择引脚
	gpio_set_value(oled->dc_gpio, 1);

	//释放发送缓存
	kfree(buffer);

	return result;
}

static void oled_reset(struct oled_handle *oled)
{
	//暂时拉高复位引脚
	gpio_set_value(oled->rst_gpio, 1);
	msleep_interruptible(200);
	//拉低复位引脚，进行复位
	gpio_set_value(oled->rst_gpio, 0);
	msleep_interruptible(200);
	//拉高复位引脚，复位结束
	gpio_set_value(oled->rst_gpio, 1);
	msleep_interruptible(200);
}

static int oled_update(struct oled_handle *oled)
{
	int i;
	int result;
	uint8_t command[3];

	for(i=0; i<8; i++)
	{
		command[0] = 0xb0+i;				//设置页地址
		command[1] = 0x10;					//设置显示位置—列高地址
		command[2] = 0x00;					//设置显示位置—列低地址
		result = oled_write_cmd(oled, command, 3);
		if(result != 0)
			return result;
		result = oled_write_data(oled, oled->fb_buffer[i], 128);
		if(result != 0)
			return result;
	}

	return 0;
}

static int oled_init(struct oled_handle *oled)
{
	int result;
	uint8_t command[28];

	//复位OLED
	oled_reset(oled);

	//发送初始化命令
	command[ 0 ] = 0xAE;				//--turn off oled panel
	command[ 1 ] = 0x00;				//---set low column address
	command[ 2 ] = 0x10;				//---set high column address
	command[ 3 ] = 0x40;				//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
	command[ 4 ] = 0x81;				//--set contrast control register
	command[ 5 ] = 0xCF;				 // Set SEG Output Current Brightness
	command[ 6 ] = 0xA1;				//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
	command[ 7 ] = 0xC8;				//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
	command[ 8 ] = 0xA6;				//--set normal display
	command[ 9 ] = 0xA8;				//--set multiplex ratio(1 to 64)
	command[ 10] = 0x3F;				//--1/64 duty
	command[ 11] = 0xD3;				//-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
	command[ 12] = 0x00;				//-not offset
	command[ 13] = 0xD5;				//--set display clock divide ratio/oscillator frequency
	command[ 14] = 0x80;				//--set divide ratio, Set Clock as 100 Frames/Sec
	command[ 15] = 0xD9;				//--set pre-charge period
	command[ 16] = 0xF1;				//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
	command[ 17] = 0xDA;				//--set com pins hardware configuration
	command[ 18] = 0x12;				
	command[ 19] = 0xDB;				//--set vcomh
	command[ 20] = 0x40;				//Set VCOM Deselect Level
	command[ 21] = 0x20;				//-Set Page Addressing Mode (0x00/0x01/0x02)
	command[ 22] = 0x02;				//
	command[ 23] = 0x8D;				//--set Charge Pump enable/disable
	command[ 24] = 0x14;				//--set(0x10) disable
	command[ 25] = 0xA4;				// Disable Entire Display On (0xa4/0xa5)
	command[ 26] = 0xA6;				// Disable Inverse Display On (0xa6/a7) 
	command[ 27] = 0xAF;				//--turn on oled panel 
	result = oled_write_cmd(oled, command, 28);
	if(result != 0)
		return result;

	//更新OLED显示
	return oled_update(oled);
}

static ssize_t oled_write(struct file *file, const char __user *data, size_t lenght, loff_t *pos)
{
	int result;
	uint32_t id;

	//提取设备ID
	id = MINOR(file->f_inode->i_rdev);
	if((id > OLED_NUMBER) || (lenght < OLED_DISPLAY_RAM_SIZE))
		return -EINVAL;

	//将显示内容拷贝到显存
	if(copy_from_user(oled_handle[id].fb_buffer, data, OLED_DISPLAY_RAM_SIZE))
		return -EFAULT;

	//更新显示
	result = oled_update(&oled_handle[id]);
	if(result != 0)
		return result;

	return OLED_DISPLAY_RAM_SIZE;
}

static int oled_mmap(struct file *file, struct vm_area_struct *vma)
{
	int result;
	uint32_t id;
	unsigned long offset;
	unsigned long virt_start;
	unsigned long pfn_start;
	unsigned long size;
	unsigned long vmstart;

	//提取设备ID
	id = MINOR(file->f_inode->i_rdev);
	if(id > OLED_NUMBER)
		return -EINVAL;
	//获取应用层传递的偏移，vm_pgoff以页大小为单位
	offset = vma->vm_pgoff << PAGE_SHIFT;
	if(offset != 0)
		return -EINVAL;
	//计算偏移后的内存地址
	virt_start = (unsigned long)oled_handle[id].fb_buffer;
	//根据内存地址计算物理地址页框号
	pfn_start = (unsigned long)vmalloc_to_pfn((void*)virt_start);
	//计算欲映射的大小
	size = vma->vm_end - vma->vm_start;
	//虚拟地址起始值，可能是用户指定，可能是系统分配
	vmstart = vma->vm_start;
	//最大不超过全局内存大小
	if(size > OLED_DISPLAY_RAM_SIZE)
		size = OLED_DISPLAY_RAM_SIZE;
	//映射大小必须是PAGE_SIZE的整倍数
	if((size % PAGE_SIZE) != 0)
		size = (size + PAGE_SIZE) / PAGE_SIZE * PAGE_SIZE;
//	//增加no cache属性
//	//映射后应用层进行读写操作时不经过cache，因为驱动层用vmalloc分配使用cache，所以应用层也要使用cache
//	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

	printk("phy: 0x%lx, offset: 0x%lx, size: 0x%lx\r\n", pfn_start << PAGE_SHIFT, offset, size*PAGE_SIZE);
	while (1) 
	{
		//进行内存映射，每次映射一页，因为vmalloc申请的内存可能不连续
		result = remap_pfn_range(vma, vmstart, pfn_start, PAGE_SIZE, vma->vm_page_prot);
		if (result) 
			break;
		else
			printk("virt_start = 0x%lx, pfn_start = 0x%lx, vmstart = 0x%lx, size = 0x%lx\r\n", virt_start, pfn_start << PAGE_SHIFT, vmstart, PAGE_SIZE);

		//映射完成
		if(size <= PAGE_SIZE)
			break;

		//剩余大小递减
		size -= PAGE_SIZE;
		//虚拟地址递加
		vmstart += PAGE_SIZE;
		//全局内存地址递加
		virt_start += PAGE_SIZE;
		//根据内存地址计算物理地址页框号
		pfn_start = vmalloc_to_pfn((void *)virt_start);
	}

	if(result) 
		printk("remap_pfn_range failed at \r\n");

	return result;
}

static long oled_ioctl32(struct file *file, unsigned int request, unsigned long arg)
{
	int result;
	uint32_t id;

	//提取设备ID
	id = MINOR(file->f_inode->i_rdev);
	if(id > OLED_NUMBER)
		return -EINVAL;

	//检查设备类型
	if(_IOC_TYPE(request) != OLED_MAGIC)
		return -EINVAL;

	//提取命令码
	switch(_IOC_NR(request)) 
	{
		case 0:
			result = oled_update(&oled_handle[id]);
			break;
		default:
			result =  -EINVAL;
			break;
	}

	return result;
}

static long oled_ioctl64(struct file *file, unsigned int request, unsigned long arg)
{
	return oled_ioctl32(file, request, arg);
}

//设备和驱动匹配成功执行
static int oled_probe(struct spi_device *spi)
{
	int result;
	uint32_t id;
	struct device *device;

	printk("%s\r\n", __FUNCTION__);

	//获取设备ID号
	result = of_property_read_u32(spi->dev.of_node, "id", &id);
	if(result != 0)
	{
		printk("get dev id failed\r\n");
		return result;
	}
	if(id > OLED_NUMBER)
	{
		printk("dev id invalid\r\n");
		return -EINVAL;
	}

	//检查是否占用
	if(oled_handle[id].use_flag) 
	{
		printk("dev id repetition\r\n");
		return -EINVAL;
	}
	spi->dev.driver_data = (void*)id;

	//标记为已占用
	oled_handle[id].use_flag = 1;
	//分配显存
	oled_handle[id].fb_buffer = vmalloc(OLED_DISPLAY_RAM_SIZE);
	//设置默认显示状态
	memset(oled_handle[id].fb_buffer, 0x00, OLED_DISPLAY_RAM_SIZE);
	//绑定SPI设备
	oled_handle[id].spi_device = spi;
	/* 初始化spi_device */
	/*MODE3，CPOL=1，CPHA=1*/
	oled_handle[id].spi_device->mode = SPI_MODE_3;
	spi_setup(oled_handle[id].spi_device);

	//获取RST GPIO号
	oled_handle[id].rst_gpio = of_get_named_gpio(spi->dev.of_node, "rst-gpios", 0);
	if(oled_handle[id].rst_gpio < 0)
	{
		vfree(oled_handle[id].fb_buffer);
		oled_handle[id].use_flag = 0;
		printk("get rst_gpio failed\r\n");
		return oled_handle[id].rst_gpio;
	}
	//申请RST GPIO
	result = gpio_request(oled_handle[id].rst_gpio, "oled,rst_gpio");
	if(result != 0)
	{
		vfree(oled_handle[id].fb_buffer);
		oled_handle[id].use_flag = 0;
		printk("request rst_gpio failed\r\n");
		return result;
	}
	//设置复位引脚输出高电平
	gpio_direction_output(oled_handle[id].rst_gpio, 1);

	//获取DC GPIO号
	oled_handle[id].dc_gpio = of_get_named_gpio(spi->dev.of_node, "dc-gpios", 0);
	if(oled_handle[id].dc_gpio < 0)
	{
		gpio_free(oled_handle[id].rst_gpio);
		vfree(oled_handle[id].fb_buffer);
		oled_handle[id].use_flag = 0;
		printk("get dc_gpio failed\r\n");
		return oled_handle[id].dc_gpio;
	}
	//申请DC GPIO
	result = gpio_request(oled_handle[id].dc_gpio, "oled,dc_gpio");
	if(result != 0)
	{
		gpio_free(oled_handle[id].rst_gpio);
		vfree(oled_handle[id].fb_buffer);
		oled_handle[id].use_flag = 0;
		printk("request dc_gpio failed\r\n");
		return result;
	}
	//设置数据/命令选择引脚输出高电平
	gpio_direction_output(oled_handle[id].dc_gpio, 1);

	//初始化OLED屏幕
	result = oled_init(&oled_handle[id]);
	if(result != 0)
	{
		gpio_free(oled_handle[id].dc_gpio);
		gpio_free(oled_handle[id].rst_gpio);
		vfree(oled_handle[id].fb_buffer);
		oled_handle[id].use_flag = 0;
		printk("oled_init failed\r\n");
		return result;
	}

	//创建设备文件，将ID作为此设备的次设备号
	device = device_create(oled_class, NULL, oled_num+id, NULL, "oled-%d", id);
	if(IS_ERR(device))
	{
		gpio_free(oled_handle[id].dc_gpio);
		gpio_free(oled_handle[id].rst_gpio);
		vfree(oled_handle[id].fb_buffer);
		oled_handle[id].use_flag = 0;
		printk("device create failed");
		return PTR_ERR(device);
	}

	return 0;
}

//设备或驱动卸载时执行
static int oled_remove(struct spi_device *spi)
{
	uint32_t id;

	printk("%s\r\n", __FUNCTION__);

	id = (uint32_t)spi->dev.driver_data;
	if(!oled_handle[id].use_flag)
	{
		printk("verification failed\r\n");
		return -EINVAL;
	}

	device_destroy(oled_class, oled_num+id);
	gpio_free(oled_handle[id].dc_gpio);
	gpio_free(oled_handle[id].rst_gpio);
	vfree(oled_handle[id].fb_buffer);

	oled_handle[id].use_flag = 0;

	return 0;
}

static int __init oled_drv_init(void)
{
	int i;
	int err = 0;

	printk("%s\r\n", __FUNCTION__);

	for(i=0; i<OLED_NUMBER; i++)
	{
		memset(&oled_handle[i], 0, sizeof(oled_handle[i]));
	}

	//根据次设备号起始值动态注册字符设备号
	err = alloc_chrdev_region(&oled_num, 0, OLED_NUMBER, "atk,oled");
	if(err != 0)
	{
		printk("alloc chrdev failed\r\n");
		return err;
	}
	printk("first device major %d, minor %d\r\n", MAJOR(oled_num), MINOR(oled_num));

	//初始化CDEV对象
	cdev_init(&oled_cdev, &oled_ops);
	//向系统添加CDEV对象
	err = cdev_add(&oled_cdev, oled_num, OLED_NUMBER);
	if(err != 0)
	{
		unregister_chrdev_region(oled_num, OLED_NUMBER);
		printk("add cdev failed\r\n");
		return err;
	}

	//创建class对象
	oled_class = class_create(THIS_MODULE, "oled,class");
	if(IS_ERR(oled_class))
	{
		cdev_del(&oled_cdev);
		unregister_chrdev_region(oled_num, OLED_NUMBER);
		printk("class create failed");
		return PTR_ERR(oled_class);
	}

	//注册SPI驱动
	err = spi_register_driver(&oled_drv);
	if(err != 0)
	{
		class_destroy(oled_class);
		cdev_del(&oled_cdev);
		unregister_chrdev_region(oled_num, OLED_NUMBER);
		printk("add cdev failed\r\n");
		return err;
	}
	return err;
}

static void __exit oled_drv_exit(void)
{
	printk("%s\r\n", __FUNCTION__);

	//注销SPI驱动
	spi_unregister_driver(&oled_drv);
	//销毁class对象
	class_destroy(oled_class);
	//从系统删除CDEV对象
	cdev_del(&oled_cdev);
	//注销字符设备号
	unregister_chrdev_region(oled_num, OLED_NUMBER);
}

module_init(oled_drv_init);
module_exit(oled_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("LF");
MODULE_DESCRIPTION("oled_dev");
